home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Diamond Collection
/
The Diamond Collection (Software Vault)(Digital Impact).ISO
/
cdr48
/
386p_200.zip
/
TECH.TXT
< prev
next >
Wrap
Text File
|
1995-01-14
|
60KB
|
1,317 lines
386POWER (sort of) PROGRAMMER GUIDE
-------------------------------------------------------------------------------
Introduction, warnings and credits
SMILEY WARNING:
I use "smileys" (the faces drawn with punctuation marks and other chars
that you must roll your head to the left to see clearly)
a lot because i like to write as i talk
and i heavily use pronunciation inflection.
(read: the things i say, if they are written as they are,sometimes look like
an offense or a menace, the smiley correct this).
;-) or ;) == it's a joke, you know i don't mean exactly this.
:-) or :) == happy
:-} or :} == oops!
:-( == i'm not happy of this, will make it better next time
PG-A386 WARNING:
386POWER HAS BEEN RATED Programmer Grade - Assembly 386 !!!!! ;)
I suppose you know enough of assembly and 386 programming.
If you don't, try the following crash program
a) Get an access to Internet (direct or by way of a friend)
(Internet is the big international net i put 386P 2.00
for public distribution)
b) Learn how to use the FTP program to get access to the huge resources
available on the ftp servers connected to Internet.
If you can, look into the x2ftp.oulu.fi ftp server, it's an ibm pc
programmer heaven, there, into the /pub/msdos/programming directory
you can find all the infos and tools you need:
1) A zipped file containing a complete 386 programming manual
by Intel (nearly perfect, if you can forgive some typos here and there).
2) The docs describing VCPI,DPMI and VDS.
3) The Ralph Brown Interrupt list (lots of info about
all the function calls you can find working with a pc compatible)
4) Dos-extenders better than mine :}.
Just in case you think mine is not good enough, look for pmode305.zip
You can "attach" it to the basic 386Power
and get complete DPMI support for most of the ms-dos configuration
you can find (raw,XMS,VCPI,DPMI).
ASM warning:
This thing is like a shotgun, don't look into its barrel when it's loaded.
Running under VCPI this baby is capable to tunnel thru the
protection levels and do nasty things if programmed to do this.
Be also aware that most of current assemblers perform "automatic"
code optimizations, i usually test my programs with no optimizations allowed
so it is easier to find my bugs or the assembler's bugs
(believe me, assemblers have their own bugs).
I take no responsability for this, this code is not harmfull
since it is intended mostly for videogames, but intentionally
or not you can modify it to do very dangerous things.
Be also aware that even cpus have their bugs and quirks
so test your program on different cpus if you can.
LM WARNING: THIS will scare you ;).
As you can guess from my name and address, i'm italian
and to say the worse i'm from the northen Italian region called Veneto
more precisely from a place 40km away from Verona
(you know, Romeo and Juliet, the Arena)
here in Veneto there is an old popular motto
saying "Veronesi tutti matti" (Verona citizens are all crazy)
and i'm not an exception :).
This means i think in a very weird way
(for example: i prefer well documented 386 assembly to C )
and hate writing documentation like this
(i prefer putting comments inside the include files ).
I'm so crazy i'd like to find a job as a game programmer/designer
(nearly zero probability here in Italy).
What's more i hate to overtest things, i test them only for what i need
(don't use this code for an x-ray machine control program
if you don't like the idea to see people glowing in the dark ;) )
and i'm never sure to have eradicated all the bugs
(but i try hard to do this, quality control and bug eradication is planned
before starting coding).
If you find some comments or documentation quite hard to understand
remember some of it has been written while under "berseker coding fury"
( picture me typing like a maniac at 00:30 a.m.
and yelling "Ah! This code is so neat that explains itself!"
while the radio volume is set to 'sonic boom' level)
( not to mention i haven't lots of freetime).
And remember that sometimes i write in italo-venetian-english too
(when i write english things quickly, i keep writing in english using
phrase structures similar to italian or venetian).
Thomas Pytel's PMODE:
I owe lots of thanks to Thomas Pytel for distribuiting the source code
of his dos-extender.
Since 386Power is sort of PMODE stepbrother initially i based this
sort of tech manual on the pmode.doc file included into pmode.zip
by Thomas "Tran" Pytel, this way it was easier to get the differences
between the two extenders. Now ... well the dos-extender has grown a lot
(and degenerated a lot, ulp!) and tech.txt too.
You might say i mention Tran too many times (or too few times
if you are Tran ;) ), fact is that i never read a book about
386 protected mode programming before starting code this
(don't get me wrong, i had years of experience programming
everything from the Z80 to the MC68000)
but read a lot about other things related to this
and i already knew real mode assembly programming very well,
i just looked into tran docs and played with pmode, then i found the
DPMI docs and the Ralph Brown Interrupt List on the internet
and decided to try to build 386Power.
What's more i found in the net a complete book in electronic format
about 386 programming with all the info one may need
(look in the x2ftp.oulu.fi site).
Anyway, Tran docs together with the pmode sources were the most complete
thing i found about protected mode programming with some things
you hardly find in the docs (like how to make VCPI paging work).
Fact was that Tran coding style was a lot different from mine
and used self modifying code, PMODE was a wonderful thing
but wasn't exactly what i wanted, so i restarded from scratch
keeping and eye on compatibility.
If you are familiar with the OLD pmode dos-extender
OR with the old 386power dos-extender, read carefully
there are some things that are similar but a lot different.
By the way, as far i know, Tran has produced new and very powerful
dos-extenders, if you just need a dos-extender, look for the things
he recently distribuited on the 'net.
If you think the 386Power dos-extender is "not enough" for you
but you still want to keep the support routines, get a
dos-extender providing a DPMI interface (like Tran's PMODE 3.05)
and use 386Power in DPMI mode together with it (but you'll have to
modify the 386P startup code a little, to set correctly some system vars)
(by the way, if you choose to do this, you can remove the VCPI stuff
from 386power.asm and make your program slimmer).
VERSION WARNING:
386P is still evolving (look at the XGE driver docs)
so remember to give a look at the code (AND to the include files)
if something doesn't work as you expect from the docs.
------------------------------------------------------------------------------
Introduction
386POWER (386P for short ) has been conceived and coded by
Lorenzo Micheletto (that's me) but is not all "my stuff" (besides mostly is).
It is based on the PMODE dos-extender by Tran (a.k.a. Thomas Pytel)
386P uses modified algorithms found in the old PMODE 2.24.
I'm not the guy who reinvents the wheel every time (just NEARLY every time ;) ).
I wanted to build a GAME ENGINE capable to run under 32bit protected mode.
A game engine is the game equivalent of a data base engine, a set of software
modules you can use as a base to build lots of different games with the
same underlying "animation and sound model".
Think about games like Commander Keen 1,2,3,4,5,6 , Duke Nukem 1,2,3,4,5
Bio Menace, Cosmo, etc. etc. They ALL follow the same "model", if they were
designed using the same set of basic modules handling sound,video and animation
once the first game was build, the other would have needed only different
sound and graphics plus minor game engine updates.
(Well, if you look at Commander K. you will notice 1,2,3 uses the same engine
and 4,5,6 uses an evolution of the previous engine)
From MY point of view (well, i bet somebody has a better point of view)
to build a games engine running in protected mode the following things
were needed:
a) dos-extender module (386Power), to get access to full 32bit power.
b) timekeeping/graphics/text/blitter/game_control_input/sound modules (XGE)
c) finite state automata engine (to program objects motion
and "rules of action" of moving object using a table driven
automata engine).
< getting here you can easily build a Commander Keen-like game
or a cool board game like Settlers >
d) geometry engine (poligon or bitmap oriented)
< getting here you can build a 3d game >
Included with 386P are the XGE modules ( a) and b) ), the other things
are up to you.
------------------------------------------------------------------------------
Why use it?
This little thing lets you run a 32bit protected mode program
on nearly any computer with a 386 or better.
It can run ...
On MS-DOS with an VCPI/DPMI server (like EMM386, QEMM and others).
[NO! It won't run on plain real mode, i think it is better to
run on a trusted "mode switch manager" to smooth out hardware
differences]
AND WITH XSM and "raw 386", now! (but these later modes
are not that robust in this "forced to be early" release).
In a full-screen MS-Windows dos-box (or dos session) using the DPMI server
included with MS-Windows running in 386 enhanced mode
(see the included *.PIF files to see how to configure a 386powered program
to run under Windows without "warnings").
In an OS/2 dos-box (maybe this will need a little tweaking of the dos-box
setup file).
In any other OS with a decent 386+MS-DOS emulator
(even on risc workstations!!!!).
Supporting "only" VCPI and DPMI this baby does not need to run complex
configuration procedures because EMM386 (the ms-dos VPCI server)
is installed by default by the ms-dos install program
and the Windows DPMI server is always present.
I'm also adding XMS and "raw 386" (HARD) support but i'm not happy of this
because i just saw that the some program needs that extra kbyte
that only an XMS or "raw" setup can make available.
Running in 32bit (nearly) flat protected mode you will get access to
a) linear addressable memory up to 4Gbyte
b) 32bit addressing and data
(this means you will never have to worry about loading segment registers
and other things like that). This means a lot more speed if you understand
the strong points and the weak points of protected mode.
------------------------------------------------------------------------------
ENVIRONMENT INITIALIZATION:
386Power boots from a ms-dos environment (even if emulated)
checks if there is a 32bit (or more) intel compatible CPU (386,486,Pentium,..)
checks if a VCPI or DPMI manager is present
(read: it needs EMM386, QEMM, Windows running in 386 Enhanced Mode
or OS/2 or at least a VCPI or DPMI server)
checks for XSM or "nothing can help me, i'll have to go 'raw 386' "
and if it finds one it initialize the protected mode environment
for 32bit FLAT PROTECTED mode (one big segment spanning 4Gbyte).
Once in Protected Mode IRQs are active and redirected to their
real mode default handlers.
ALL "ms-dos interrupt vectors" gets copied to the _OldInt table
(and restored on program termination) so you don't need to save them
and if something goes crazy, there are good changes that the _Exit
routine will be capable to return the system into a "safe" situation.
The IRQ mask gets saved, once the program terminates it gets restored
(this should take care of berseker irqs).
------------------------------------------------------------------------------
THE ENVIRONMENT
First of all, the memory layout, as you can guess, is nearly the same of PMODE:
Your program will have at least 3 segments
code16 A 16bit segment that holds all the real mode and 16bit protected
mode init,exit code and real mode irq handlers.
It has to be the first segment of the EXE.
code32 The huge 32bit segment. You can throw in as much code as will fit
in low memory (no 64k fixup overflows). If you need more code
space, you're gonna have to load it into extended memory at runtime
and use it there.
Under protected mode, addresses (code and data, they're the
same memory space) are offset from the beginning of this segment.
I'll explain later how to access things outside this segment
if you really need to.
codeend This MUST be the last segment in the EXE. It is the base for the
stack and low memory allocation.
If you want to add other segments insert them between
code32 and codeend
The space used by code16+code32+codeend can be as big as the space available
in low memory (the free memory available to plain ms-dos programs)
this usually means between 400k...610k
(it's not a real limit as you will see, because you can put all the data
into extended memory)
two things seems to miss ...
1) The STACK...
The stack is shared by your protected mode program AND real mode calls.
This means it can be at most 64k wide (don't worry it's wide enough)
and must be in low memory (where dos code can access it directly).
The stack region always begins at codeend, and goes on for STACKSIZE paragraphs.
(a value declared in 386power.inc)
but the stack the main program can use can be at most STACKUSER paragraphs
because the rest is reserved for "temporary stacks"
needed to switch from protected mode to real mode and back (more on this later).
2) The HEAP....
There are TWO HEAPS you can allocate memory from at run time.
The "Low memory" heap, which covers all conventional memory below A0000h
(the memory accessible to 16bit ms-dos code).
The "Extended memory" heap, which covers that big and fat block of memory
above the first megabyte of memory (where only your 386powered program
can go).
There's a reason for keeping them separate
(other than that big hole between A0000h and 100000h
which I did not want to fill in or rearrange with paging):
MS-DOS real mode can only see the low memory heap so low memory is "precious".
In calls to ms-dos where you have to pass buffer addresses
you must pass only buffers located in low memory.
Low memory is the place where you would allocate any critical disk or
DMA buffers (more on this later).
------------------------------------------------------------------------------
VIRTUAL REGISTERS
You call real mode interrupts and far routines with "virtual registers".
They are memory images of the registers as they will be set for the real mode
interrupt. When the mode-switch occours, the current register values
are pushed into the stack, then the mode switch occours
and the NEW register VALUES are LOADED FROM the virtual registers table.
So.. if you set the V86eax variable to 10h
when you switch to real mode, the eax register will contain 10h.
Then when you are back, you can look at the last value of
"eax in real mode" looking into V86eax.
------------------------------------------------------------------------------
Details of runtime:
After initialization of the protected mode environment
386POWER will call a label called _Main located in the code32 segment
(so place _Main: where you want to start your code
and define it as a PUBLIC variable
i.e:
public _Main
_Main:
<insert here your startup code>
)
When _Main is reached you can assume that:
1) The stack is set up and it is STACKUSER*16 bytes wide.
2) The interrupts are disabled (and have been all the way from real mode)
just in case there's something you want to do before enabling them.
3) CS points to the code segment you're running in (code32)
by way of a selector set up by 386POWER.
4) DS,ES,FS, and SS point to an alias of the code segment (same memory,
but write permissions activated) (in protected mode the memory
referenced thru CS is read-only, so we need another selector
pointing to the same memory but with read/write permissions).
N.B. Both selectors in 3) and 4) are set with a 4Gigabytes limit
so you can stuff anything you want into them.
5) GS is a segment that's 4Gigabyte wide but starts at absolute address 0.
This is useful for accessing the real mode dos data area, or
the BIOS data area, or the PSP, etc...
to access something with real-mode address r_segment:r_offset
just use gs:(r_offset+(r_segment*16)).
6) ALL interrupt vectors are copied to the _OldInt table
(they will be restored on interrupt termination)
so you can change the dos interrupt vectors without having to be worried
to save 'em.
7) If you run under DPMI
and you need locked memory (i.e. for IRQ handling) use the DPMI functions
(check the _386Man var. and call the DPMI lock function if
_386Man anded with IS_DPMI is different from zero )
386P tries to lock the memory it allocates, but if the lock fails
it goes ahead because maybe you don't need it.
8) If you run under VCPI, 386P goes for overkill, because it runs at CPL0
where no operating system can stop it.
Man/woman/person/alien warned, half saved, the other half is up to you.
I usually test my programs under DPMI, then if nothing lethal happens
i try to see how they work under VCPI (where they run faster
and have more memory available).
9) If you run under DPMI, an IRQ happened while in "real mode"
gets reflected to the protected mode handler.
IF YOU DON'T RUN UNDER DPMI, an irq happened in real mode
goes to the real mode handler, but 386Power substibutes
the real mode handler with an "irq reflector" that switches
control to the protected mode handler and back.
If an irq is not "redirected", the real mode handler is called
"directly" (so we can spare two mode switches and go faster).
There are two exceptions to this rule, IRQ 0 (int 8)
and IRQ 1 (int 9) are not "automatically reflected"
(see into 386Power.inc the description of _SetIRQ for more info)
Selectors:
There are three main selectors you have to know.
_SelCode, _SelData, and _SelZero are 16bit word vars you can access to get
the selector values for the code, data, and zero (GS) segments respectively.
When 386power pass control to _Main you can assume
CS=_SelCode, DS=ES=FS=SS=_SelData, and GS=_SelZero.
You can change the segment register if you wish
(for example to do a REP MOVS in the zero seg).
But the 386POWER routines and ints expect the segregs to be these values
And these MUST be the values when you jump to '_Exit' to return to DOS.
Another thing that is assumed by 386POWER is DF=0
(direction flag is clear (like the CLD instruction)).
You can perform STD, but before calling 386POWER stuff you have to
"re-set" the direction flag with CLD.
Memory layout:
Linear address selector: Usage:
00000000h _SelZero ## MS-DOS code/data
##
##
_Code16Base <none visible to you> @@ Your program code starts here
@@ and here you will find
@@ the 16 bit code16 segment
@@ that initializes
@@ your program to prot. mode
_Code32Base _SelCode,_SelData || code32 segment starts here
|| the lower part of if contains
|| the 386P dos extender
|| static data and
|| 32bit system code
||
|| After that there will be your
|| program's code
|| and static data
||
||
||
$$ codeend segment
$$ here there will be your stack
$$
$$
$$
$$
LL Here starts the "low" heap
LL
LL
LL
LL
LL
LL
LL
LL
LL
LL
LL
000A0000h VV Here starts the VGA video
VV memory "address window"
VV
VV
000C0000h RR Here starts the 384k block
RR used to map EMS pages and
RR UMBs (remapped blocks of ram)
RR where you place the things
RR you "loadhigh"
RR and ROMs
RR
RR
RR
RR
RR
RR
00100000h XX Extended memory starts here
XX and is usually present other
XX operating system code
XX
XX
XX
.. Here starts the "high" heap
.. (actually extended memory)
.. it is usually more than
.. one megabyte, here you should
.. place all the big chunks
.. of data you need.
..
..
..
..
..
-------------------------------------------------------------------------------
Linear & relative addresses:
All addresses of thing declared in code32 are
RELATIVE to the beginning of the code32 segment
(which could start anywhere in low memory).
nearly all the system code expects addresses that are code32 relative.
For this reason, you must adjust any physical memory pointers
before you use them.
That is, to access something at linear address A0000h
(A000:0000, in 16 bit seg:ofs notation) you can use GS
(it is loaded with the _SelZero selector) and write to GS:0A0000h.
If you want to write to A0000h using the default data segment
(because it is faster to do so)
it will not be at DS:A0000, but at
DS:(A0000h - linear_address_of_the_beginning_of_Code32).
This linear_address_of_the_beginning_of_Code32
is stored in a variable called '_Code32Base'.
So if the segment code32 is 1F43, the linear address will be 1F430h
To get a code32-relative pointer to linear address A0000h
you will have to do something like:
mov eax,0A0000h
sub eax,_Code32Base
; now ds:eax points to the same location of linear address 0A0000h
To convert the code32-relative offset of MyVar to a linear address
you will have to do something like:
mov eax,offset MyVar
add eax,_Code32Base
; now gs:eax points to the same location of code32:MyVar
The linear address for code16 is also provided in _Code16Base.
As well as the linear address of the PSP in '_PSPBase'.
The linear addresses of code16 and PSP will always be less than code32.
To access them (memory pointed to by them, these vars are in code32), you will
have to use one of two methods. One is easy enough, just use the GS segment.
Or you could use negative indexes from the normal segment, causing a
4Gbyte wraparound (a lot like the 64kbyte wraparound under real mode).
However it is rare to access things outside code32, so my advice is to use GS
to make things clear and as a "marker" of things "done outside code32".
Only when you really need speed (i.e. to access the vga memory)
translate the base address once and store it into a variable.
Whew! Confused? Look into 386file.asm to see ho this works, it is easier
to see than to explain.
-------------------------------------------------------------------------------
THE MEMORY HEAPS: Memory where is my memory?
You have two heaps, into low and high (extended) memory.
Each of which is guaranteed to be at least as much as you specified in LOWMIN
and EXTMIN in 386power.inc, the startup code will grab all low memory for you
(because it's meant to run standalone), and it will attempt
to grab all the high memory it can.
Two dword variables hold information about each memory area.
_LoMemBase
and
_LoMemTop
specify the base and top of the low memory pool as code32-relative
addresses (ready to use, no adjustment needed).
The total amount of low memory available in bytes is _LoMemTop - _LoMemBase
(notice _LoMemTop points to one byte beyond the last available byte).
The _GetLoMem routine is a very simple routine that takes a length in EAX
and checks to see if there is enough low memory.
If there is enough, it adds the length to _LoMemBase and
returns a pointer (code32 relative, ready to use) in EAX
to the low memory block along with the carry flag clear.
If it finds not enough memory, it returns with the carry flag set.
_HiMemBase, _HiMemTop, and _GetHiMem are the same thing for high memory.
Then there is the _GetMem routine that first tries to allocate
low memory and if this fails it tries high memory.
-------------------------------------------------------------------------------
Calling real (actually quite virtual) mode:
"Real mode" here is used as equivalent to "virtual 8086 mode"
(emulated real mode), when you run under VCPI and DPMI
you cannot get to "true" real mode, but your 16bit "real mode" code
won't notice the difference.
You can call real mode, and back. This is only provided so that
you can call real mode interrupts, and routines that you can't recode
in protected mode (like the _SetCPUWindows vesa bios function).
Keep these cross-mode calls to the minimum because they eat lots of cpu time
and can raise some nasty "interaction" bugs.
You can call real mode interrupts or procedures from protected mode
through CALL _ExecReal (execute real mode far proc), and
CALL _ExecINT (execute real mode int).
These function calls are only available
to the PROTECTED MODE part of your program.
To pass register values from protected mode to real mode and back
you use 'virtual registers'.
These 'virtual registers' are merely memory images of EAX,EBX,ECX,EDX,ESI,EDI,
EBP,DS,ES,FS,and GS. AL and AH and AX and BL ... etc ... are there too, and
they share the appropriate memory space with each other so if you change the
'virtual' AH register, the 'virtual' AX and EAX registers will be changed
accordingly.
The virtual registers are called V86eax,V86ax,V86ah,V86al and so on...
There are no SS,ESP,CS,EIP registers. CS:EIP is taken from the real mode
interrupt vector table (call _ExecINT) or from the processor's CX:DX registers
(call _ExecReal).
SS:ESP is set up by 386POWER together with the extended memory manager
providing protected mode services.
@ Call _ExecINT : Do a real mode interrupt.
AL=interrupt you want to do. All the virtual registers will
be passed to the real mode handler.
They will also be passed back as the
return values into the virtual register table.
The carry, zero, aux, parity, sign, and overflow flags will
be passed back as the actual CPU flags. The real mode interrupt will be
called with interrupts disabled (as it is usually). Keep in mind, no CPU
registers will be modified (except the flags mentioned). Only their "virtual"
images will be changed by the real mode int handler.
REMEMBER that _ExecINT uses the "real mode INT" table found at program start
(the one copied to the _OldInt table), this looks weird, but it is
very useful under VCPI when you need "reflected IRQ" service routines.
@ Call _ExecReal: Call a real mode far procedure with interrupts disabled.
CX:DX=seg:off you want to call.
The register passing works just like _ExecINT.
------------------------------------------------------------------------------
Things to know about IRQs:
Upon startup, all the interrupt vectors for IRQs point to routines that
redirect the IRQs to their default real mode handlers. You can hook into any
IRQ you want. There are two dword pointers that allow you to get and set IRQ
vectors.
_GetIRQ and _SetIRQ point to (32bit near) routines to get and set the
relative address of the handler for specific IRQs within the code32 segment.
To get the address of a handler, just do a 'call _GetIRQ' with BL set to the
IRQ num you want (0-15). EDX will be returned pointing to its current handler.
To set an IRQ, pass BL again as the IRQ number, and EDX as the offset of the
new handler. You can chain to the old handler if you want just by jumping to
the old address when your handler is done processing.
(see specific things about irqs under VCPI
in the VCPI specific section some pages after this).
If you have to enable or disable an IRQ line use the
_GetIRQMask and _SetIRQMask functions, this way your application
will work on anything supported by 386P.
Irq handlers in 32bit protected mode are terminated by IRETD.
When your IRQ handler is called, you can be sure of ONLY TWO THINGS.
The IF flag is clear and CS is loaded with _SelCode.
All the general regs and segregs should be treated as undefined.
Even SS cannot be trust, because under DPMI it is set to a little
"interrupt stack" set up by DPMI.
In other words ... DON'T CHANGE STACK!!!!
And don't assume SS=DS=CS, if you want access DS, reload it with cs:_SelData
and if you access things using ebp (i.e. mov eax,[ebp] )
remember SS may not be equal to DS (so use mov eax,ds:[ebp]).
Another consideration for DPMI is the IF flag. According to DPMI specs, only
CLI, STI, and INT 31h functions AX=900h and AX=901h should be counted on to
modify the interrupt flag (POPF(D) and IRET(D) should not). This is because
certain DPMI systems might have to virtualize the interrupt flag, and keep the
real flag enabled at all times (but don't worry, if the 'virtual' flag is
clear, your program will not get any IRQs). In practice, certain DPMIs do
allow IRET(D)s and POPF(D)s to modify the virtual interrupt flag. But this is
inconsistent across them. So you should follow these rules:
@ CLI and STI are allowed, and do their functions.
@ Don't assume anything about POPF(D) and IRET(D) and the interrupt flag.
@ Don't assume the interrupt flag PUSHF(D) stores on the stack is correct,
it might be the real flag or the 'virtual' flag.
@ These DPMI INT 31h functions are supported under VCPI too
(by way of the 386POWER interface).
) AX=900h: Get state of IF and disable it. Returns AL set to the IF flag.
) AX=901h: Get state of IF and enable it. Returns AL set to the IF flag.
) AX=902h: Only returns AL set to the IF flag (0=disabled, 1=enabled).
@ At the end of an IRQ handler, put a STI. When the handler is called, flags
are automatically disabled.
If you do not reenable them, and neither does the IRETD...
Well... under some systems it will run and on other it will hang.
-----------------------------------------------------------------------------
How the stack is shared between modes:
386POWER uses the same stack for both pmode and real mode. This stack is
always located in low memory (always locked under DPMI).
The total size of the stack is set as STACKSIZE in 386power.asm.
There are other values there called STACKUSER,STACKSWTR & STACKSWTP
that needs further explanations.
Your program starts running from the _Main label
with a stack width of STACKUSER paragraphs, so your program can
use up to STACKUSER paragraphs of stack space.
When a mode switch occurs FROM PROTECTED MODE TO REAL MODE
the new stack is the old stack base minus STACKSWTR paragraphs.
(STACKSWTR == STACK SWitch To Real mode)
This happens when you execute _ExecINT, _ExecReal
or and irq handlers "chains back" to the real mode irq handler.
The stack base is the stack location when your program
starts. And it is only modified by mode switches.
When a mode switch occurs FROM REAL MODE TO PROTECTED MODE
the new stack is the old stack base minus STACKSWTP paragraphs.
(STACKSWTP == STACK SWitch To Protected mode)
This happens under VCPI when an irq handler gets reflected to prot. mode.
When a routine "switches back", the stack it used is destroyed
and the previous stack is selected again.
That is, the whole stack structure uses STACKSIZE paragraphs
but when the program is running it can see only
a "local stack" (let's call it a slot) of variable width
depending on "what is running" (main program or "mode switched stuff").
Into each slot you can safely allocate local variables on the stack
referencing them with esp ad ebp without having to worry of mode switches
or reflected irqs triggering a mode switch.
Every time you switch mode, a new slot is used and when you "get back"
to the caller, the caller's slot becomes the current stack again.
Let's make an example:
Immagine the following "chunks of code"
A is the main program
B is a real mode routine
C is a real mode timer driven interrupt service routine
D is a protected mode routine
To describe the stack status we will use the following notation:
XYZ
123--- a stack structure with six slots and slots 1,2,3 "in use"
MRP where 3 is the "active stack" and is in use by the Z subroutine
slot 1 is the "MAIN" slot (STACKUSER wide)
slot 2 is a "real mode switch" slot (STACKSWTR wide)
and slot 3 is "prot mode switch" slot (STACKSWTP wide)
1] A is running
A
1-----
M
2] A calls B (mode switch)
Now B can switch stack if it wants to
but the "current slot" from the point of view of 386P
is still SLOT 2
AB
12----
MR
3] B calls D (mode switch)
As i said, B may have changed its stack, but the mode-switch code
doesn't care of this neither has to check what B did
it just says "well, slot 2 is in use, better get to slot 3".
ABD
123---
MRP
4] routine C triggered by timer interrupt , and the irq handler is in real mode
(unexpected mode switch you cannot control)
As usual, another switch happens.
ABDC
1234--
MRPR
5] interrupt handler returns
ABD
123---
MRP
6] D returns
AB
12----
MR
7] B returns
A
1-----
M
As you can guess, you'd better keep enough slots available because
if too many mode switch "accumulates" the stack structure will break.
386P up to release 1.01 used only one "slot size", now there are three
slot sizes:
STACKUSER for the main program and the routines in protected mode it calls
usually i set it to 16..32kbytes
STACKSWTR for every call from protected mode TO real mode.
STACKSWTP for every call from real mode TO protected mode.
usually is set the "mode switch" stacks to
1..4kbytes
Usually the allocation of STACKSWTR/P slots is caused by IRQ and
"mode switch" calls so these don't use lots of stack space
(256..512 bytes --> 10h .. 20h para) and can be small.
Under DPMI things are different but the rule
"the less mode switches, the better" is still true.
DPMI handles stack switching on its own, any IRQ causes a switch
to a totally different stack provided by DPMI (argh!).
To summarize:
@ In an IRQ handler, DON'T switch off the stack it is entered with. Which is
not guaranteed that SS=_seldata.
@ Don't do too many nested calls across modes.
@ You CAN safely assume SS=_seldata in protected mode only in your main stream
of execution (read: not inside an Interrupt Service Routine)
@ Consider your maximum effective stack size to be STACKUSER while
executing "non mode switched" code.
@ You CAN call across modes using INT32/33 from an IRQ handler
but remember this takes time
(giving IRQs enough time to stockpile if they "accumulate" fast enough )
and if you do it the wrong way, the calls get into an infinite loop.
@ You'd better handle IRQs from prot mode only and let 386P do
the nasty irq-reflection work for you.
@ If you call ms-dos/bios functions from an IRQ handler, remember most of them
are NOT reentrant and the actual virtual-machine switches can be different
from what you expects.
@ remember to use IRETD to return control to the main program
IRET is the "16 bit" interrupt return and you are in 32bit mode.
-------------------------------------------------------------------------------
Exception handling:
Under DPMI are handled entirely by the DPMI host.
Under VCPI, exceptions cause immediate termination
(maybe in future releases i will include some debug messages when
terminating due to an exception).
------------------------------------------------------------------------------
Potential DMA and address mapping problems:
As you know, the DMA controllers in the PC use physical addresses.
Nothing but the processor itself knows how linear memory is arranged in the
physical memory banks. When paging is disabled, the relationship is very
simple. The linear address is always the same as the physical address. But
when you enable paging, that could get all screwed up.
Under VCPI and DPMI paging IS enabled (bad luck, uh?).
You can almost definately count on extended memory addresses
not being consistent with their physical addresses.
Low memory however, will usually map perfectly to its physical addresses.
Usually i said, NOT always.
The point is that you shouldn't use "raw" DMA under VCPI and DPMI.
To handle DMA you should follow the Virtual DMA Specification (VDS).
This is the recommended way of handling DMA under VCPI and DPMI.
See the Ralph Brown interrupt list for INT 4Bh for more info.
I've provided a simple VIRTUAL DMA MANAGER [included into 386P]
to handle DMA with or without a Virtual DMA Server.
The current release handles any DMA controller up to ISA bus systems
i plan to add EISA and PCI support (to get faster DMA transferts)
as soon as someone sends me some docs.
Handling "just ISA" means it is not possible to do DMA to/from memory
above the first 16Mbyte (EISA ans PCI can support up to 4Gbyte of addresses).
I hope the current VDS servers included into EMM managers and Windows
can "remap" correctly if you try to do dma above the ISA addressing limit.
The main reason to use dma is to make sound, i've included the EXPERIMENTAL
sources of a virtual dma module and chunks of my sound system
"still under devenlopement"
and into the dos-extender there is a function to "map-in"
physical addresses (i still have to test it because to do so
i need a board with ram addresses to map in) (for example: a super vga board
with a "linear mapping" addressing mode like the ET4000)
------------------------------------------------------------------------------
And now to discuss some of the finer points of different protected mode
environments:
VCPI:
VCPI is nearly "raw" mode. The CPL is 0, and no op-sys routine can trap
into your code if you don't want to.
Paging is enabled, but there is no "virtual memory manager"
(i.e. a thing that can "swap" blocks of memory to/from disk and handle
sharing of address spaces and other thing like that)
(read: nothing will try to "move your memory" under your feet)
if you want you can mess directly with the page tables (terrific, uh?!?).
The problem comes with the way VCPI compatibility works. To call a real mode
interrupt or procedure, we have to pass protected mode control back to the
VCPI server. This comes out to one thing. IRQs that occur in a real mode call
MAY NOT make it to your protected mode handler. It's just the way VCPI works.
Under VCPI the real mode interrupt table and the protected mode
interrupt table belongs to different Task Segment Descriptors.
386P CAN PLACE an "irq redirector" handler into the real mode task
so if the irq happens in real mode and a "new" irq is installed
in protected mode, the prot mode handler will be called.
Well, it looks perfect, isn't it ? Not so perfect, under VPCI and DPMI
there can be MULTIPLE PROTECTED MODE APPLICATIONS RUNNING!!!
And the irq redirector works only from the "real mode task" to
the "386P task" and back, if the IRQ happens while into
another VCPI application you have to hope that "the unknown task"
is a friendly one ( one that reflects to real mode the irqs
it doesn't know how to handle) and what's more, the VCPI/DPMI server
may "look at what's happening" and virtualize things you don't expect
to be virtualized.
Anyway, worry not! This is the worst case scenario.
It's all? Not.
There are two other things you have to remember:
a) IRQs needs to be serviced as quickly as you can, or you'll get
irq overruns and you'll have to send SEOI (specific EOI) codes
to tame the beast.
If an irq gets reflected from real to protected mode
(to let the p. mode handler give a look at it) and then it is
reflected back to real mode
(to let the ms-dos irq handler give a look at it too)
and then you step back where you started ....
Well, (BAAAAAD!) it takes FOUR mode-switches to go back and forth!!!
b) When you switch, the "mode switch" code does not have to touch
the hardware that caused the irq.... this is really hard if the
interrupting device was the keyboard controller
(that little chip that handles keyboard and other little things
INCLUDING THE A20 LINE that every mode-switch has to handle).
So forget to reflect directly to protected mode if the irq was from
the keyboard controller.
To avoid these problems, when you call _SetIRQ under VCPI
the 386Power system code "places" in the real mode interrupt table
a routine that redirects the irq to the protected mode handler
ONLY IF THE IRQ IS NOT one of the "VCPI most wanted" irqs
(IRQ 0 and IRQ1, also known in real mode as INT 8 and INT 9)
If you do want to handle irq 0/1 in real mode too, you can
but you need to set the real mode handler yourself, calling
the ms-dos get/set irq functions by way of _ExecINT.
N.B. Look into 386timer.asm how to
handle directly an irq call in both protected mode and real mode.
ANYWAY, when you "restore" the "default" irq handler under protected mode
the 386Power system code AUTOMATICALLY restores the
"default" real mode handler too!!!
------------------------------------------------------------------------------
DPMI:
DPMI is not that bad.
It could let you select CPL0 for "trusted" applications.
I don't like the overhead imposed by CPL3
(in CPL3, certain instructions have to be emulated by software).
Multitasking virtual machines in general are not that hot
when you're trying to do a timing critical action game.
One really annoying problem with DPMI is that current implementations are
far from perfect.
QDPMI 1.01 for example, dies when an IRQ occurs in a prot. mode
call from a real mode IRQ.
DPMI docs say this shouldn't happen, and it doesn't under Windows 3.1 DPMI
implementation.
The Windows 3.1 DPMI is a little better but has lethal quirks
that caused me quite big headaches.
Hmm, another little problem is that I'm not sure how many DPMIs out there
actually reflect IRQs to real mode if they occur in protected mode.
Windows 3.1 seems to send them all over as it should.
According to Tran QDPMI 1.01 sends IRQ1, but not IRQ0.
And it also doesn't seem to pass IRQs that occur in real mode
through their protected mode handlers, while Windows 3.1 does.
Under DPMI, the dpmi server should reflect interrupts for you
(as happens under Win 3.1).
------------------------------------------------------------------------------
XMS / HARD
As i said elsewere, these two "nearly raw" (XMS) and "totally raw" (HARD)
"operating modes" are activated when 386P does not detect a
protected mode interface.
This means "linear" memory addresses are equivalent to physical memory
addresses, paging is not active .... AND WE HAVE TO "talk" to hardware
and processor directly.
This also means that every hardware incompatibility cannot be "masked out"
by system software.
there is more extended memory available but there are more possibilities
of error.
There are no exceptions handler (this would require P.I.C. remapping
and may break on "new" computer featuring "improved" P.I.C.)
and if an exception happens this usually means your system hangs up.
------------------------------------------------------------------------------
Some misc notes:
@ Under VCPI, 386Power will map as much extended memory as it can, up to
56M without allowing the page tables to use up more memory than would leave
LOWMIN. Allocating up to 56M means that extended memory under VCPI
can be at most 55M (even if there is more available) plus up to
4M of "mapped ram" (ram mapped into the address space from a physical device)
@ Before exiting your program, you do NOT need to restore any vectors
(protected or real mode) nor the typematic rate.
And you do not have to restore the IRQ masks at 21h and A1h (PMODE stores
them before jumping to _main, and restores them before exiting).
BUT if you reprogrammed the irq8/int70h timer interval, set it back to its
initial value
(the irq0/int8 is automatically set back to 18.2 Hz by the _Exit routine).
@ If you're gonna add other 16bit segments, put them in between code32
and codeend.
@ Remember that upon reaching '_Main', interrupts are still disabled. Don't
forget to do the STI.
INTERSEGMENT ACCESS:
If you look into 386power.asm you will see that the 16bit initialization code
uses the 32bit segment as a data segment, i did it to prevent
"automatic grouping" and other "smarts" that current assembler performs
that sometime can disrupt what your code is designed to do.
Anyway the data accessed by the "little" code16 segment must be the first
into the "big" code32 segment so
WHEN LINKING, put 386power.obj FIRST on the list of obj files you pass
to the linker (this way you are sure the 32bit code in 386power
is the nearest to the code16 segment) and check if the segment ordering
is SEQUENTIAL (it is possible to turn on alphabetic ordering on some
assembler/linkers).
if you look into 386P remember:
d16_ means DPMI stuff in code16
d32_ means DPMI stuff in code32
v16_ means VCPI stuff in code16
v32_ means VCPI stuff in code32
s16_ means "raw" stuff in code16
s32_ means "raw" stuff in code32
"raw" == shared between VCPI/DPMI xor "dos-extending" code
handle it with care, it bytes!!!
------------------------------------------------------------------------------
Heres a list of other vars provided by PMODE to your program:
_LoMemBase:dword
Low mem base for allocation (first free byte).
_LoMemTop:dword
Top of low mem (last free byte +1).
_HiMemBase:dword
High mem base for allocation (first free byte).
_HiMemTop:dword
Top of high mem (last free byte +1).
_PSPBase:dword
Absolute linear address of start of PSP.
_Code16Base:dword
Absolute linear address of start of code16.
_Code32Base:dword
Absolute linear address of start of code32 (32bit code offset from this).
_SelCode:word
Code segment selector.
_SelData:word
Data segment alias selector for code.
_SelZero:word
Data segment starting at absolute 0.
_386Man:byte
386 manager type:
0=VCPI == program runs in CPL0
real mode irqs are not reflected by default to protected mode.
Paging enabled, but no virtual memory.
1=DPMI == program runs in CPL3
real mode irqs are always reflected to prot. mode
at least the full DPMI 0.9 is available
and you can use the int 31h functions to see if
DPMI 1.0 is present.
Paging enabled, virtual memory enabled (memory pages
may be swapped to/from disk)
2=XMS == program runs in CPL0
Initialization i performed using the XMS functions
then the program "drives the 386" directly.
(still under testing)
3=HARD == Absolutely no extended memory or mode-switch support
from ms-dos, the program runs at CPL0 and has total control.
(still under testing)
_CPUPower:byte
Contains the CPU type detected at initialization:
3 = 386
4 = 486sx/dx
5 = Pentium
N.B No checks are done about the FLOATING POINT capabilities!!!!
_386Return: dword
Contains the code32 relative offset to the first message string
the _Exit function displays when terminating a 386Power program.
The end of the string pointed by _386Return must be marked with '$'
because it is written to screen using the dos function int 21h, ah=09h.
_386Terminator:byte
This is the first byte of the standard "termination" program.
If you modified _386Return and want to set it back to the
standard message, simply use the following instruction:
mov _386Return, offset _386Terminator
_GetIRQ:dword
A pointer to the get IRQ function appropriate for the mode.
The function takes arguments as follows:
In:
BL == IRQ num (0-0fh)
Out:
EDX == offset of current IRQ handler
_SetIRQ:dword
A pointer to the set IRQ function.
In:
BL == bit 0..5: IRQ num (0-15 are the only allowed values)
bit 6: IRQ REFLECTOR STRATEGY
0 = "KILL" REAL MODE IRQ ( only if BIT7 is set and not DPMI)
1 = REFLECT REAL MODE IRQ TO PROT. MODE HANDLER
(only if BIT7 is ser and not DPMI)
bit 7: ACTIVATE CUSTOM IRQ REFLECTOR STRATEGY
0 = Let the extended mode server handle irqs from real mode
as it likes (usually VCPI servers do not reflect irqs)
1 = Enable "direct control" of this irq when it happens
in real mode ( "activate" bit6 )
EDX == offset of new IRQ handler to set.
_OldInt:table of 256 dwords LOCATED IN THE CODE32 SEGMENT
It contains all the original real mode int vectors found at
386P startup. Instead of storing "old" real mode interrupts yourself
386P does it for you.
------------------------------------------------------------------------------
And now some 'functions'. Remember, they ALL need:
CS=_SelCode, DS=ES=FS=_Seldata, GS=_SelZero, DF=0 (CLD).
_GetMem:
Allocate any mem, (first checks low, then high)
In:
EAX == size requested
Out:
CF=0 memory allocated
CF=1 not enough mem
EAX == relative pointer to mem or undefined if not enough
_GetLoMem:
Allocate some low mem
In:
EAX == size requested
Out:
CF=0 memory allocated
CF=1 not enough mem
EAX == relative pointer to mem or undefined if not enough
_GetHiMem:
Allocate some high mem
In:
EAX == size requested
Out:
CF=0 memory allocated
CF=1 not enough mem
EAX == linear pointer to mem or undefined if not enough
_MapPhysmem:
Maps into address space a physical memory block of locations
used by a physical device.
in: eax=physical address base
edx=size in bytes
out:
if CARRY CLEAR then
eax= equivalent code32 relative offset
else error, no mapping performed.
This function is useful when you need to map
into the program's address space the "linear addressable video-ram"
provided by some supervga graphics cards.
Usually those "memory windows" are enabled by th eprogrammer, so are
not visible to the vcpi or dmpi manager, this functions
lets you access "new addresses" using the code32 segment.
N.B. When running under VCPI the initialization code leaves space
for a maximum of 4Mbytes of "new ram"
and 56Mbyte of already allocated ram.
_GetIRQMask:
Get status of IRQ mask bit (at port 21h or A1h)
In:
BL == IRQ num (0-15)
Out:
AL == status: 0=enabled, 1=disabled
_SetIRQMask:
Set status of IRQ mask bit
In:
BL == IRQ num (0-15)
AL == status: 0=enabled, 1=disabled
_OnExit:
Appends a subroutine BEFORE the current exit code
in: EAX= code32 relative offset of routine to call
N.B. the "appended" routine must terminate with a RET and preserve
all the general porpouse registers EAX..EBP and segment registers
and it can only assume CS=_SelCode,DS=_SelData.
_Exit:
Exit to real mode, restore initial IRQ mask
restore timer 0 frequency to 18.2 Hz
restore ALL interrupt vectors
and get out setting video mode 03 (color text 80x25)
if you really need to deal directly with DMA, check into 386power.inc
for the "dma support" routines _DMAInit,_DMAInfo,_DMAMap,_DMAUnMap,
_DMALock,_DMAUnLock,_DMASend,_DMAReceive
------------------------------------------------------------------------------
PROGRAM SUPPORT
I'm not going to go commercial with 386Power and its companion code.
Maybe i will go commercial with the games i will produce with it.
Anyway i will distribuite the sources of this and ALL the future releases
of 386power and its companion code (the XGE stuff).
If 386Power doesn't work on your system i'd like to know (so i can fix it)
you can reach me at the addresses listed in the end.
Call me if you have suggestions about new improvements and things like that.
BUT REMEMBER, 386power is NOT TOTALLY FREE (gosh!)(actually is nearly free)
See the DISTRIBUTION AGREEMENT LICENSE.DOC file.
If you modify 386P (not just cosmetic adjustments of course)
you should use a different name (to avoid confusion)
BUT you have to include almost the following message
"based on the 386Power dos-extender by Lorenzo Micheletto MCHLNZ67T19C890A
and on the PMODE dos-extender by Thomas 'Tran' Pytel".
What's more if you make a commercial thing with it (NOT a commercial
dos-extender library !!) ... send me a free copy :).
I won't ask anything more from you.
It is not a free lunch ... it is a nearly free lunch.
The main reason i coded 386P was for pure fun and get skilled about
protected mode programming before coding the real game
(if i just wanted a dos-extender i would have bought one from FlashTek).
I distribuite it because: (in priority order)
a) Needed a wider test base (just my 386 and the 486s' of some friends
is a too restricted test base).
b) I owe it to all the people who distribuited their sources on the net
and let me learn how to handle the pc hardware and more.
c) Need to get a reputation as a programmer, sooner or later
when i will try to publish/sell my games, it could be useful.
BY THE WAY, 386P 2.00 has to be considered a "partial alpha" release
because it wasn't my intention to distribuite it, some people asked me
about some routines i included into it, so i decided it was a good thing
to send "all the package" instead of a bit here and there.
------------------------------------------------------------------------------
Final things:
386Power has been designed with a restricted goal to reach
(kick my code into 32bit protected mode :} and be sure that on program
termination no bad things will happen).
You are free to use it and change it any way you want, but please
don't make protected mode viruses or trojan horse, i really hate those
losers that think they are smart because they can destroy things.
If you think you are smart, design and code a terrific piece of software.
And as i said, 386P is limited to VCPI and DPMI (if you call it a limit).
If you want a "real" dos-extender capable to handle anything
contact the FlashTek guys and buy it
or search the latest pmode release by Tran (Thomas Pytel) on internet
or get Watcom C++ with the DOS4GW dos-extender
or anything else you like.
To reach FlashTek try to ask about them in the rec.games.programmer
internet newsgroup.
To find Tran, do the same or ...
....as far i know on may 1993 he could be reached on
Creativity Demo Net or SBCnet as 'Tom Tran'
or at the Sound Barrier: (718)979-6629, (718)979-9406
or on Internet: tran@phantom.com
If you want to contact me (for bug reports or other things)
you can reach me ....
On the internet as:
knight@maya.dei.unipd.it
(I heard calls from Germany may fail sometimes, if you fail to
send e-mail from there, retry two or three times, it is
a stocastic bug i think)
By plain mail at the following address:
Lorenzo Micheletto
Via Piazza Miega 10/A
Veronella (VERONA)
ITALY 37040
N.B. Maybe in 1995 i will be reachable only by plain mail
( i will be in the italian army for a year)
but after that (february 1996) i will sure be back to Padova
WITH A "FULL POWER" 386P 3.00 release that hopefully
take care of all the incomplete things in release 2.00
and will give you all you need to make game engines.